package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.tianji.learning.constants.LearningConstants.POINTS_BOARD_TABLE_PREFIX;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author xzh
 * @since 2024-03-18
 */
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    private final StringRedisTemplate redisTemplate;
    private final UserClient userClient;
    private final PointsBoardMapper pointsBoardMapper;

    /**
     * 获得天梯榜排名信息
     *
     * @param query
     * @return
     */
    @Override
    public PointsBoardVO queryPointsBoardPageList(PointsBoardQuery query) {
        //1.获得本赛季的key
        LocalDate now = LocalDate.now();
        Long season = query.getSeason();
        String key= RedisConstants.BOARD_KEY_PREFIX+now.format(DateUtils.BOARD_DATE_SUFFIX_FORMATTER);
        //2.确定是查本赛季还是历史赛季
        //3.查询自己的积分排名
        PointsBoard pointsBoard=season==null||season==0? queryMyCurrentBoard(key):
                                 queryMyHistoryBorad(season);
        //4.查询天梯榜排名
        List<PointsBoard> boardList=season==null||season==0?
                queryCurrentPointsBoardList(key,query.getPageNo(),query.getPageSize()):
                queryHistoryPointsBoardList(query);

        //封装 返回
        // 获得用户姓名
        if (CollUtils.isEmpty(boardList)){
            return null;
        }
        List<Long> list = boardList.stream().map(PointsBoard::getUserId).collect(Collectors.toList());
        List<UserDTO> userDTOS = userClient.queryUserByIds(list);
        Map<Long, String> map = userDTOS.stream()
                .collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));

        List<PointsBoardItemVO> itemVOList=new ArrayList<>(list.size());
        for (PointsBoard board : boardList) {
            PointsBoardItemVO vo=new PointsBoardItemVO();
            vo.setName(map.get(board.getUserId()));
            vo.setRank(board.getId().intValue());
            vo.setPoints(board.getPoints());
            itemVOList.add(vo);
        }
        PointsBoardVO vo=new PointsBoardVO();

        vo.setRank(pointsBoard.getId().intValue());
        vo.setBoardList(itemVOList);

        return vo;
    }

    /**
     * 创建历史积分榜单
     * @param season
     */
    @Override
    public void creatHistoryPointsBoard(Integer season) {

        PointsBoardMapper.creatHistoryPointsBoard(POINTS_BOARD_TABLE_PREFIX+season);
    }

    /**
     * 查询历史天梯榜排名
     * @param query
     * @return
     */
    private List<PointsBoard> queryHistoryPointsBoardList(PointsBoardQuery query) {
        //1.获得数据库名称
        String tableName= POINTS_BOARD_TABLE_PREFIX+query.getSeason();
        //2.分页查询
        List<PointsBoard>list=pointsBoardMapper
                .queryHistoryPointsBoarPage(tableName,query.getPageNo(),query.getPageSize());
        if (CollUtils.isEmpty(list)){
            return CollUtils.emptyList();
        }
        return list;
    }

    /**
     * 查询当前天梯榜排名
     * @param key
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public List<PointsBoard> queryCurrentPointsBoardList(String key, Integer pageNo, Integer pageSize) {
        // 开始所有
        int start=(pageNo-1)*pageSize;
        //1.获得用户id和得分
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate
                .opsForZSet().reverseRangeByScoreWithScores(key, start, start + pageSize - 1);
        if (CollUtils.isEmpty(tuples)){
            return CollUtils.emptyList();
        }
        //2.手动排名  1.的返回结果是降序排名
        Long rank=start+1L;
        List<PointsBoard> list=new ArrayList<>(tuples.size());
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            PointsBoard p=new PointsBoard();
            p.setUserId(Long.getLong(tuple.getValue()));
            p.setPoints(tuple.getScore()==null?0:tuple.getScore().intValue());
            p.setId(rank);
            rank++;
            list.add(p);
        }
        //3.返回
        return list;
    }

    /**
     * 查询自己历史的积分排名
     * @param season
     * @return
     */
    private PointsBoard queryMyHistoryBorad(Long season) {
        //1.获得当前用户Id
        Long userId = UserContext.getUser();
        //1.2动态拼接数据库名
        String tableName= POINTS_BOARD_TABLE_PREFIX+season;
        //2.去数据库查询信息
        PointsBoard p=pointsBoardMapper.queryMyHistoryBorad(userId, tableName);
        //3.返回
        return p;
    }

    /**
     * 查询自己当前的积分排名
     * @param key
     * @return
     */
    private PointsBoard queryMyCurrentBoard(String key) {
        String userId = UserContext.getUser().toString();
        //1.绑定Key
        BoundZSetOperations<String, String> ops = redisTemplate.boundZSetOps(key);
        //2.获得用户积分
        Double point = ops.score(userId);
        //3.获得用户排名 倒叙 第一名是0 所以要+1
        Long rank = ops.reverseRank(userId);
        //4.封装返回
        PointsBoard p=new PointsBoard();
        p.setPoints(point==null? 0:point.intValue());
        p.setId(rank==null? 0: rank +1);
        return p;
    }
}
